# Lilidream制作的关于气象的库 LiMeteo
from netCDF4 import Dataset
from prettytable import PrettyTable
from datetime import datetime, timedelta
import numpy as np
import matplotlib.pyplot as plt
from cartopy import crs as ccrs
from cartopy import feature as cfeature


class consoleLogger:
    def __init__(self, name, printMsg=True):
        self.name = name
        self.printMsg = printMsg
        self.buffer = []
        self.ml = False

    def log(self, message):
        if self.printMsg:
            print("│ "*len(self.buffer) +
                  "┌[LiMeteo."+self.name+"]: "+message+"...", end="\n")
            self.buffer.append(message)

    def done(self):
        if self.printMsg:
            print("│ "*(len(self.buffer)-1) +
                "└[LiMeteo."+self.name+"]: "+self.buffer[-1]+"  已完成", end="\n")
            self.buffer.pop()


class ERA5Reader:
    """
    ERA5Reader类，用于读取ERA5数据
    """

    def __init__(self, level_file_path, land_file_path=None, msg=None):
        """
        读取数据文件，并初始化数据
        @param file_path: 数据文件路径
        """
        # load files
        self.level_file_path = level_file_path
        self.data = Dataset(level_file_path)
        self.land_file_path = land_file_path
        if land_file_path is not None:
            self.landData = Dataset(land_file_path)

        # load level data
        self.keys = self.data.variables.keys()
        self.lon = self.data.variables['longitude'][:]
        self.lat = self.data.variables['latitude'][:]
        self.lonRange = [self.lon.min(), self.lon.max()]
        self.latRange = [self.lat.min(), self.lat.max()]
        self.time = self.data.variables['time'][:]

        # load land data
        if land_file_path is None:
            self.landKeys = []
            self.landLon = []
            self.landLat = []
            self.landLonRange = [0, 0]
            self.landLatRange = [0, 0]
            self.landTime = []
        else:
            self.landKeys = self.landData.variables.keys()
            self.landLon = self.landData.variables['longitude'][:]
            self.landLat = self.landData.variables['latitude'][:]
            self.landLonRange = [self.landLon.min(), self.landLon.max()]
            self.landLatRange = [self.landLat.min(), self.landLat.max()]
            self.landTime = self.landData.variables['time'][:]

        # multi-level
        if 'level' in self.keys:
            self.level = self.data.variables['level'][:]
        else:
            self.level = None

        # metadata
        self.meta = {}
        for k in self.keys:
            self.meta[k] = self.data.variables[k].long_name

        self.landMeta = {}
        for k in self.landKeys:
            self.landMeta[k] = self.landData.variables[k].long_name

        self.msg = msg

    def convert_time(self, hoursSince19000101, timeZone=8, fmt="%Y-%m-%d %H:%M:%S"):
        """
        将ERA5的时间格式时间转换为可阅读形式
        @param secondsSince19000101: 距离19000101的小时数，ERA5数据集时间维度的值
        @param timeZone: 时区，默认为8，表示东八区，即+8小时，西区使用负值
        @param fmt: 输出时间的格式, datetime.strftime的格式
        """
        t = datetime(1900, 1, 1) + timedelta(hours=1) * \
            hoursSince19000101 + timedelta(hours=1) * timeZone
        return t.strftime(fmt)

    def printInfo(self, land=False):
        """
        打印读取的数据的信息
        """
        dimensions = PrettyTable(['Name', 'Size', "Values"])
        dimenKeys = self.landData.dimensions.keys() if land else self.data.dimensions.keys()
        data = self.landData if land else self.data
        keys = self.landKeys if land else self.keys
        for d in dimenKeys:
            if d == "level":
                val = ' '.join([str(v) for v in data.variables['level'][:]])
            else:
                val = ' '.join([str(v) for v in data.variables[d][:4]]) + \
                    ' ... ' + ' '.join([str(v)
                                       for v in data.variables[d][-4:]])
            dimensions.add_row([d, data.dimensions[d].size, val])
            if d == "time":
                dimensions.add_row(['(FmtTime)', data.dimensions['time'].size, self.convert_time(self.time[0]) + ' ... ' + self.convert_time(self.time[-1])
                                    + "(CST) ; dt = " + str(self.time[1] - self.time[0]) + "h"])

        variables = PrettyTable(['Name', 'Longname', 'Shape', "units"])
        for v in keys:
            if v not in dimenKeys:
                variables.add_row([v, data.variables[v].long_name, ' '.join([data.variables[v].dimensions[i]+":"+str(
                    data.variables[v].shape[i]) for i in range(len(data.variables[v].shape))]), data.variables[v].units])

        print("Dimensions:")
        print(dimensions)
        print("Variables:")
        print(variables)

    def convertTimeBack(self, strTime, timeFmt="%Y%m%d%h", timeZone=8, land=False):
        """
        将时间转换为时间索引，若无则返回-1
        """
        t = datetime.strptime(strTime, timeFmt) - timedelta(hours=1) * timeZone
        h = int((t - datetime(1900, 1, 1)).total_seconds() / 3600)
        timeList = self.landTime if land else self.time
        if h in timeList:
            return np.where(timeList == h)[0][0]
        else:
            return -1

    def convertLevelBack(self, level):
        """
        将层级转换为索引，若无则返回-1
        """
        if self.level is False:
            raise Exception("No level dimension")
        if level in self.level:
            return np.where(self.level == level)[0][0]
        else:
            raise Exception("No such level")

    def getSubset(self, variableName, startTimeIndex, endTimeIndex, startLon, endLon, startLat, endLat, levels=[], lowRes=1):
        """
        获取一部分的数据，矩形切割
        @param variableName: 变量的名称
        @param startTimeIndex: 开始时间的索引，可用 convertTimeBack() 函数获取
        @param endTimeIndex: 结束时间的索引，可用 convertTimeBack() 函数获取
        @param startLon: 开始经度，单位：度
        @param endLon: 结束经度，单位：度
        @param startLat: 开始纬度，单位：度
        @param endLat: 结束纬度，单位：度
        @param levels: list，数据高度层，输入气压值获取特定层，如[925, 850, 500]，若为空则返回全部层数据
        @param lowRes: 数据点获取间隔数，1表示全部获取，2表示每两个数据点获取一个值（间隔1个数据点），以此类推
        """

        # 判读变量所在数据
        if variableName in self.keys:
            level = True
        elif variableName in self.landKeys:
            level = False
        else:
            raise Exception("数据中无此变量：%s \n No such variable in the data: %s" % (
                variableName, variableName))

        if startTimeIndex < 0 or endTimeIndex < 0 or startTimeIndex >= self.time.size or endTimeIndex >= self.time.size:
            raise Exception(
                "时间索引超出范围，请检查输入的时间索引\nTime index out of range, please check the input time index")
        if startLon < self.lonRange[0] or endLon < self.lonRange[0] or startLon > self.lonRange[1] or endLon > self.lonRange[1]:
            raise Exception(
                "经度超出范围，请检查输入的经度\nLongitude out of range, please check the input longitude")
        if startLat < self.latRange[0] or endLat < self.latRange[0] or startLat > self.latRange[1] or endLat > self.latRange[1]:
            raise Exception(
                "纬度超出范围，请检查输入的纬度\nLatitude out of range, please check the input latitude")

        lon = self.lon if level else self.landLon
        lat = self.lat if level else self.landLat
        keys = self.keys if level else self.landKeys

        startLonIndex = np.where(lon == startLon)[0][0]
        endLonIndex = np.where(lon == endLon)[0][0]
        startLatIndex = np.where(lat == startLat)[0][0]
        endLatIndex = np.where(lat == endLat)[0][0]

        if startLonIndex > endLonIndex and startLatIndex > endLatIndex:
            raise Exception(
                "开始经纬度必须小于结束经纬度\n Start longitude and latitude must be less than end longitude and latitude")

        self.msg.log(
            "正在读取数据: "+(self.meta[variableName] if level else self.landMeta[variableName]))
        if 'level' in keys:
            if len(levels) == 0:
                levels = self.level
            levelIndex = [self.convertLevelBack(l) for l in levels]
            data = self.data.variables[variableName][startTimeIndex:endTimeIndex,
                                                     levelIndex, endLatIndex:startLatIndex+1:lowRes, startLonIndex:endLonIndex+1:lowRes]
        else:
            if level:
                data = self.data.variables[variableName][startTimeIndex:endTimeIndex,
                                                         endLatIndex:startLatIndex+1:lowRes, startLonIndex:endLonIndex+1:lowRes]
            else:
                data = self.landData.variables[variableName][startTimeIndex:endTimeIndex,
                                                             endLatIndex:startLatIndex+1:lowRes, startLonIndex:endLonIndex+1:lowRes]

        if data.shape[0] == 1:
            data = data[0]
        if data.shape[0] == 1:
            data = data[0]

        self.msg.done()
        return data

    def getSubsetLonLat(self, startLon, endLon, startLat, endLat, lowRes=1, land=False):
        """
        获取一部分的经纬度
        """
        lonRange = self.landLonRange if land else self.lonRange
        latRange = self.landLatRange if land else self.latRange
        lon = self.landLon if land else self.lon
        lat = self.landLat if land else self.lat

        if startLon < lonRange[0] or endLon < lonRange[0] or startLon > lonRange[1] or endLon > lonRange[1]:
            raise Exception(
                "经度超出范围，请检查输入的经度\nLongitude out of range, please check the input longitude")
        if startLat < latRange[0] or endLat < latRange[0] or startLat > latRange[1] or endLat > latRange[1]:
            raise Exception(
                "纬度超出范围，请检查输入的纬度\nLatitude out of range, please check the input latitude")

        startLonIndex = np.where(lon == startLon)[0][0]
        endLonIndex = np.where(lon == endLon)[0][0]
        startLatIndex = np.where(lat == startLat)[0][0]
        endLatIndex = np.where(lat == endLat)[0][0]
        return lon[startLonIndex:endLonIndex+1:lowRes], lat[endLatIndex:startLatIndex+1:lowRes]

    def getSection(self, variableName, timeIndex, loc, dir="x", lim=None, level=None, lowRes=1):
        """
        获取垂直剖面数据。
        @param timeIndex:int 数据时间索引
        @param dir:'x' or 'y' 剖面方法，x为纬向，y为经向
        @param lim:tuple 剖面范围经度或纬度的两个值数组，如(90,180)，不填则全部范围
        @param level:tuple 气压层范围，从上往下，例如(500, 925)
        @param 水平方向上的低采样
        @return x, h, level, d  剖面图x坐标，y坐标层数与数据
        """
        # 判读变量所在数据
        if variableName not in self.keys:
            raise Exception("数据中无此变量：%s \n No such variable in the data: %s" % (
                variableName, variableName))

        if dir == "x":
            loc = np.where(loc == self.lat)[0][0]
        elif dir == "y":
            loc = np.where(loc == self.lon)[0][0]
        else:
            raise Exception("dir 只能为 'x' 或 'y'")

        if lim is None:
            if level is None:
                if dir == "x":
                    d = self.data.variables[variableName][timeIndex,
                                                          :, loc, ::lowRes]
                    x = self.lon[::lowRes]
                else:
                    d = self.data.variables[variableName][timeIndex,
                                                          :, ::lowRes, loc]
                    x = self.lat[::lowRes]
            else:
                l0 = np.where(level[0] == self.level)[0][0]
                l1 = np.where(level[1] == self.level)[0][0]+1
                if dir == "x":
                    d = self.data.variables[variableName][timeIndex,
                                                          l0:l1, loc, ::lowRes]
                    x = self.lon[::lowRes]
                else:
                    d = self.data.variables[variableName][timeIndex,
                                                          l0:l1, ::lowRes, loc]
                    x = self.lat[::lowRes]
        else:
            if dir == "x":
                lon0 = np.where(lim[0] == self.lon)[0][0]
                lon1 = np.where(lim[1] == self.lon)[0][0]+1
                if level is None:
                    d = self.data.variables[variableName][timeIndex,
                                                          :, loc, lon0:lon1:lowRes]
                    x = self.lon[lon0:lon1:lowRes]
                else:
                    l0 = np.where(level[0] == self.level)[0][0]
                    l1 = np.where(level[1] == self.level)[0][0]+1
                    d = self.data.variables[variableName][timeIndex,
                                                          l0:l1, loc, lon0:lon1:lowRes]
                    x = self.lon[lon0:lon1:lowRes]
            else:
                lat0 = np.where(lim[1] == self.lat)[0][0]
                lat1 = np.where(lim[0] == self.lat)[0][0]+1
                if level is None:
                    d = self.data.variables[variableName][timeIndex,
                                                          :, lat0:lat1:lowRes, loc]
                    x = self.lat[lat0:lat1:lowRes]
                else:
                    l0 = np.where(level[0] == self.level)[0][0]
                    l1 = np.where(level[1] == self.level)[0][0]+1
                    d = self.data.variables[variableName][timeIndex,
                                                          l0:l1, lat0:lat1:lowRes, loc]
                    x = self.lat[lat0:lat1:lowRes]

        # set height coordinate
        if level is None:
            level = self.level
        else:
            l0 = np.where(level[0] == self.level)[0][0]
            l1 = np.where(level[1] == self.level)[0][0]+1
            level = self.level[l0:l1]

        h = -np.log(level)/np.log(1000)+1

        return x, h, level, d


class ERA5Plotter:
    """
    ERA5数据的绘图类
    """

    def __init__(self, levelFilePath, landFilePath=None,  printMsg=True,) -> None:
        self.printMsg = printMsg
        self.msg = consoleLogger("ERA5Plotter", self.printMsg)

        self.reader = ERA5Reader(levelFilePath, landFilePath, self.msg)

        # 绘图属性
        self.timeIndex = 0  # 当前时间索引
        self.dataRange = [self.reader.lonRange[0], self.reader.lonRange[1],
                          self.reader.latRange[0], self.reader.latRange[1]]  # 数据范围
        self.lon, self.lat = self.reader.getSubsetLonLat(
            self.dataRange[0], self.dataRange[1], self.dataRange[2], self.dataRange[3])  # lowRes后的经纬度坐标
        self.titleText = []

        self.lowRes = 4

        self.contourValue = {
            "z": [0, 12000, 40],
            "d": [-0.2, 0.2, 21],
            "vo": [-0.2, 0.2, 21],
            "sh": [0, 14, 15],
            "sp": [900, 1100, 4],
            "tp": [0, 40, 21],
            "w": [-1, 1, 21]
        }
        self.cmap = {
            "t": "RdBu_r",
            "t2m": "RdBu_r",
            "q": "BuGn",
            "w": "PuOr",
            "vo": "PiYG_r",
            "d": "bwr_r",
            "tp": "GnBu"
        }
        self.colorbarConfig = {
            "shrink": 0.7,
            "pad": 0.02,
            "fraction": 0.05
        }
        self.labelFmt = {
            "z": "%.0f",
            "t": "%.0f",
            "d": "%.2f",
            "vo": "%.2f",
            "sh": "%.0f",
            "sp": "%.0f",
            "p": "%.0f",
            "w": "%.1f"
        }
        self.geoHeight = False
        self.sectionLevel = None
        self.sectionLoc = 0
        self.sectionDir = 'x'
        self.sectionLim = None
        self.geoHeightX = []
        self.geoHeightY = []

        # 变量名
        self.paramName = {
            "div": "d", "divergence": "d", "Div": "d", "Divergence": "d",
            "hgt": "z", "Hgt": "z", "Height": "z", "height": "z",
            "sh": "q", "SH": "q", "spec": "q", "Spec": "q",
            "temp": "t", "Temp": "t", "temperature": "t", "Temperature": "t",
            "uwnd": "u", "vwnd": "v", "omega": "w", "vor": "vo", "Vor": "vo", "Vorticity": "vo",
            "10muwnd": "u10", "10mvwnd": "v10", "2mtemp": "t2m", "pres": "sp", "prec": "tp"
        }

    def createFigure(self,figsize=(12, 8), subplotAdj=None, **kwargs):
        self.figsize = figsize
        self.figure = plt.figure(figsize=self.figsize, **kwargs)
        if subplotAdj is not None:
            plt.subplots_adjust(**subplotAdj)

    def close(self):
        plt.close()

    def setPreslevel(self, level):
        # 设置气压层
        if self.reader.level is not None:
            if type(level) is int:
                level = [level]
            elif level is None:
                raise Exception("请传入气压层level参数")
            else:
                raise Exception("level只能为整数\nlevel can only be an integer")
        return level

    def setLabel(self, name, level=None, land=False, unit=None, gradient=False):
        if name in self.reader.keys:
            longName = self.reader.meta[name]
            if unit is None:
                unit = self.reader.data.variables[name].units
        elif name in self.reader.landKeys:
            longName = self.reader.landMeta[name]
            if unit is None:
                unit = self.reader.landData.variables[name].units
        else:
            raise Exception("数据中无此变量名："+name)

        if level is not None:
            level = str(level[0]) + " hPa "
        else:
            level = ""
        if land:
            level = ""

        g = " Gradient" if gradient else ""

        label = level+longName+g+" ("+unit+")"
        return label

    def setTitle(self, name, level=None, suffix=None):
        if name in self.reader.meta.keys():
            longName = self.reader.meta[name]
        elif name in self.reader.landMeta.keys():
            longName = self.reader.landMeta[name]
        else:
            longName = name

        if suffix is None:
            suffix = ""
        else:
            suffix = " "+suffix

        if level is not None:
            t = str(level)+"hPa "+longName + suffix
        else:
            t = longName + suffix
        self.titleText.append(t)

    def setValue(self, value, data):
        if value is None:
            value = np.linspace(np.min(data), np.max(data), 21)
        return value

    # calculate gradient
    def calcGradient(self, data, lon, lat, lowRes):
        self.msg.log("正在计算梯度")
        earthRaidus = 6378137.0  # meter
        r = np.cos(lat*np.pi/180) * earthRaidus  # meter
        lonDis = 2*np.pi*r * lowRes / (360*4)
        lonDis = np.array([[lonDis[i] for j in range(len(lon))]
                          for i in range(len(lonDis))])
        latDis = np.ones((len(lat), len(lon))) * 2*np.pi * \
            earthRaidus * lowRes / (360*4)

        left = np.column_stack((data[:, 0], data[:, :-1]))
        right = np.column_stack((data[:, 1:], data[:, -1]))
        up = np.row_stack((data[0, :], data[:-1, :]))
        down = np.row_stack((data[1:, :], data[-1, :]))

        # create a distance coefficient matrix that each element is 2 but 1 in four edge, shape = (lat, lon)
        disCoef = np.ones((len(lat), len(lon))) * 2
        disCoef[0, :] = 1
        disCoef[-1, :] = 1
        disCoef[:, 0] = 1
        disCoef[:, -1] = 1

        # calculate gradient
        hGrad = (right - left) / lonDis / disCoef
        vGrad = (down - up) / latDis / disCoef
        grad = np.sqrt(hGrad**2 + vGrad**2)

        self.msg.done()
        return grad

    def drawMap(self, subplot=(1, 1, 1), mapRange=(0, 180, 0, 90), projection="cyl", landColor=None, oceanColor=None, coastalLineColor=None, coastLineWidth=0.8, coastalLineZorder=2, colorZorder=0, grid=True, gridLabels=True, gridColor="#444444", gridStyle=(0, (1, 3)), **gridKwargs):
        """
        为子图绘制地图底图
        @param subplot:list/tuple 子图的位置，如(2,2,1)表示共2行2列(2*2=4张子图)第一个子图.
        @param mapRange:list/tuple 地图显示范围，如[0, 180, 0, 90]表示显示0-180经度，0-90纬度
        @param projection:str 投影类型，有效值："cyl", "merc", "lambert"
        @param landColor:str 地面颜色，不输入参数则不绘制，如"#ff0000"
        @param oceanColor:str 海洋颜色，不输入参数则不绘制，如"#0000ff"
        @param coastalLineColor:str 海岸线颜色，不输入参数则不绘制，如"#ff0000"
        @param coastLineWidth:float 海岸线宽度，不输入参数则不绘制
        @param coastalLineZorder:int 海岸线的图层顺序，值越大位置越高
        @param colorZorder:int 地面/海洋填色的图层顺序，值越大位置越高
        @return:subplot 子图对象
        """
        self.msg.log("正在绘制地图")

        # 设置地图投影
        if type(projection) == str:
            if projection == "cyl":
                mapProjection = ccrs.PlateCarree()
            elif projection == "merc":
                mapProjection = ccrs.Mercator()
            elif projection == "lambert":
                mapProjection = ccrs.LambertConformal(central_longitude=(
                    mapRange[0]+mapRange[1])/2, central_latitude=(mapRange[2]+mapRange[3])/2)
            else:
                raise Exception(
                    "无效的投影类型，请检查输入的投影类型\n"
                    "Invalid projection type, please check the input projection type")
        else:
            mapProjection = projection

        # 添加子图
        self.ax = self.figure.add_subplot(
            subplot[0], subplot[1], subplot[2], projection=mapProjection)

        # 设置地图范围
        self.ax.set_extent(mapRange, crs=ccrs.PlateCarree())

        # 绘制地图特征
        if landColor is not None:
            self.ax.add_feature(
                cfeature.LAND, facecolor=landColor, zorder=colorZorder)
        if oceanColor is not None:
            self.ax.add_feature(
                cfeature.OCEAN, facecolor=oceanColor, zorder=colorZorder)
        if coastalLineColor is not None:
            self.ax.coastlines(
                color=coastalLineColor, linewidth=coastLineWidth, zorder=coastalLineZorder)

        # 绘制网格
        if grid:
            self.gridlines = self.ax.gridlines(
                draw_labels=gridLabels, color=gridColor, linestyle=gridStyle, zorder=20, **gridKwargs)
            self.gridlines.top_labels = False
            self.gridlines.right_labels = False
        else:
            self.gridlines = None

        # 清空标题
        self.titleText = []

        self.msg.done()
        return self.ax

    def show(self):
        """
        显示绘图结果
        """
        plt.show()

    def drawContour(self, name=None, level=None, value=None, data=None, lon=None, lat=None, lowRes=1, contourf=False, cmap=None, zorder=None, colorbar=True,
                    colorbarLabel=None, extend=None, CTemp=True, units=None, perks=True, label=True, fontsize=10, fmt=None,
                    color='k', dagpm=True, linewidth=1, land=False, addTitle=True, gpkg=True, **kwargs):
        """
        画等值图的基本函数。
        @param:name:str 两个nc数据内的非维度变量名称，支持扩展变量名
        @param:level:int 绘制数据的气压层，500hPa则输入500，地面变量此参数无效
        @param:value:list, ndarray 设定绘图的等值线值或等值填色的划分值，不填此参数会先从ERA5Plotter.contourValue中获取对应变量的默认值，若无默认值，则为20
        @param:data:2维数组: list, ndarray 自定义传入用于绘图的数据，若传入数组，则name不生效，同时还要传入此数据对应的经纬度坐标 lon lat
        @param:lon:1维/2维数组: list, ndarray data对应的经度坐标，对应plt.contour()的X
        @param:lat:1维/2维数组: list, ndarray data对应的纬度坐标，对应plt.contour()的Y
        @param:lowRes:int 每隔多少个格点去一个数据，用于大范围绘制时降低数据分辨率，提升速度
        @param:contourf:bool 是否绘制为等值填色图
        @param:cmap:str 等值颜色图的映射颜色名，不传入会根据ERA5Plotter.cmap设置对应变量的cmap，若无预设则为'viridis'
        @param:zorder:float 指定图层高度，默认根据类型设置高度
        @param:colorbar:bool 开关填色图色标图例
        @param:colorbarLabel:str 填色图色标图例标签，默认根据所绘变量自动填写
        @param:extend:str 设置填色超过设定值范围时的表现，有效值："neither", "both", "max", "min"，对应 plt.contourf() 的 extend
        @param:CTemp:bool 是否以摄氏度绘制温度
        @param:units:str 设置变量单位，默认自动从数据中获取
        @param:perks:bool 是否以1/(1000s)为涡度散度单位
        @param:label:bool 等值线是否绘制数值标签
        @param:fontsize:float 等值线标签字体大小
        @param:fmt:str 等值线标签格式，默认从 ERA5Plotter.labelFmt 根据变量选择，若无预设则为 "%.1f"
        @param:color:str 等值线颜色
        @param:dagpm:bool 位势高度是否使用“什位势米(dagpm)”为单位
        @param:linewidth:float 等值线宽度
        @param:land:bool 当传入自定数据 data 时，此参数设置是否为地面变量
        @param:addTitle:bool 是否自动将变量名添加到子图标题中（自动设置标题）
        @param:**kwargs: plt.contour() 或 plt.contourf() 的其他参数
        @return plt.contour() 或 plt.contourf() 的返回值
        """

        self.msg.log("正在绘制等值图 "+ name if name is not None else "")

        # set name
        if name is None and data is None:
            raise Exception("请传入变量名或数据以用于绘图")
        elif name is not None:
            if name in self.paramName.keys():
                name = self.paramName[name]
            if name in self.reader.keys:
                land = False
            elif name in self.reader.landKeys:
                land = True
            else:
                raise Exception("数据中无此变量: "+name)

        # set level to index
        if level is not None:
            level = self.setPreslevel(level)

        # get data and lon lat
        if data is None:
            data = self.reader.getSubset(name, self.timeIndex, self.timeIndex+1,
                                         self.dataRange[0], self.dataRange[1], self.dataRange[2], self.dataRange[3], level, lowRes=lowRes)
        if lon is None and lat is None:
            lon, lat = self.reader.getSubsetLonLat(
                self.dataRange[0], self.dataRange[1], self.dataRange[2], self.dataRange[3], lowRes=lowRes, land=land)

        # set default value by self.contourValue:{name:[]}
        if value is None:
            if name in self.contourValue.keys():
                value = np.linspace(
                    self.contourValue[name][0], self.contourValue[name][1], self.contourValue[name][2])
            else:
                value = 20

        # set default cmap
        if cmap is None:
            if name in self.cmap.keys():
                cmap = self.cmap[name]
            else:
                cmap = "viridis"

        # set contourf extend mode
        if extend is None:
            extend = 'both'

        # set zorder
        if zorder is None:
            zorder = 1 if contourf else 4

        # use ℃ in temperature
        if name == 't' or name == 't2m' and CTemp:
            data = data - 273.15
            units = "℃"

        # use 1/1000s in vorticity and divergence
        if name == 'vo' or name == 'd' and perks:
            data *= 1000
            units = "10**-3 s**-1"

        # set fmt
        if fmt is None:
            if name in self.labelFmt.keys():
                fmt = self.labelFmt[name]
            else:
                fmt = "%.1f"

        # set height
        if name == 'z':
            if dagpm:
                data /= 98
                value = np.arange(0, 1000, 4)
            else:
                data /= 9.8
                value = np.arange(0, 10000, 40)

        # set q
        if name == 'q' and gpkg:
            data *= 1000

        # set sp
        if name == 'sp':
            data/=100

        if contourf:
            if colorbarLabel is None:
                colorbarLabel = self.setLabel(name, level, land, units)

            c = plt.contourf(lon, lat, data, value,
                             extend=extend, cmap=cmap, zorder=zorder, transform=ccrs.PlateCarree(), **kwargs)
            if colorbar:
                plt.colorbar(c, orientation='vertical', pad=self.colorbarConfig['pad'],
                             fraction=self.colorbarConfig['fraction'], shrink=self.colorbarConfig['shrink'], label=colorbarLabel)
        else:
            c = plt.contour(lon, lat, data, value, colors=color, zorder=zorder,
                            linewidths=linewidth, transform=ccrs.PlateCarree(), **kwargs)
            if label:
                plt.clabel(c, fontsize=fontsize, fmt=fmt)

        # set title
        if addTitle:
            self.setTitle(name, level[0] if level is not None else None)

        self.msg.done()
        return c

    def dataSetting(self, time, timeFmt="%Y%m%d%H", timeZone=8, dataRange=None):
        """
        设置绘图数据的时间和范围。
        @param time:str/int 设置数据的时间，若为字符串，则按照timeFmt格式解析，默认为YYYYmmddHH，例如"2022060112"；若为整数，则为数据的时间索引。
        @param timeFmt:str 时间格式，默认为"%Y%m%d%H"，例如"2022060112"
        @param timeZone:int 时区，默认为8
        @param dataRange:list/tuple 数据范围，默认为None，即显示全部范围的数据
        @return None
        """

        # 解析时间
        if type(time) == str:
            t = self.reader.convertTimeBack(time, timeFmt, timeZone)
            if t == -1:
                raise Exception("数据中无此时间："+time+"\nNo such time in the data")
            else:
                self.timeIndex = t
        elif type(time) == int:
            self.timeIndex = time
        else:
            raise Exception(
                "无效的时间参数，请检查输入的时间参数\nInvalid time parameter, please check the input time parameter")

        self.strTime = self.reader.convert_time(
            self.reader.time[self.timeIndex])

        # 设置范围
        if dataRange is not None:
            self.dataRange = dataRange
            self.lon, self.lat = self.reader.getSubsetLonLat(
                dataRange[0], dataRange[1], dataRange[2], dataRange[3])

        return None

    def drawWindArrow(self, level=None, lowRes=8, zorder=3, color='k', width=0.002, keyX=0.9, keyY=0.9, keyValue=10, addTitle=True,  **kwargs):
        """
        绘制风箭头图。若绘制多张风箭头图，请使用**kwargs传入参数使风箭头缩放倍数固定。
        @param level:int 气压层，若为ERA5资料为单层，则此参数无效。
        @param lowRes:int 数据采样间隔数。
        @param zorder:int 图层高度，默认为55。
        @param color:str 箭头颜色，默认为黑色。
        @param width:float 箭头宽度，默认为0.002。
        @param keyX:float 箭头图例位置x坐标，相对于整个画布。
        @param keyY:float 箭头图例位置y坐标，相对于整个画布。
        @param keyValue:int 箭头图例值，默认为10m/s。
        @param **kwargs: 绘制箭头 matplotlib.pyplot.quiver() 的其他参数。
        @return: quiver:matplotlib.pyplot.quiver() 的返回值。
        """

        self.msg.log("正在绘制风矢量")
        level = self.setPreslevel(level)

        # 获取数据
        u = self.reader.getSubset('u', self.timeIndex, self.timeIndex+1,
                                  self.dataRange[0], self.dataRange[1], self.dataRange[2], self.dataRange[3], level, lowRes=lowRes)
        v = self.reader.getSubset('v', self.timeIndex, self.timeIndex+1,
                                  self.dataRange[0], self.dataRange[1], self.dataRange[2], self.dataRange[3], level, lowRes=lowRes)
        lon, lat = self.reader.getSubsetLonLat(
            self.dataRange[0], self.dataRange[1], self.dataRange[2], self.dataRange[3], lowRes=lowRes)

        # 画图
        q = plt.quiver(lon, lat, u, v, color=color, width=width,
                       zorder=zorder, transform=ccrs.PlateCarree(), **kwargs)
        plt.quiverkey(q, keyX, keyY, keyValue, str(keyValue) +
                      ' m/s', labelpos='E', coordinates='figure')
        # set title
        if addTitle:
            self.setTitle('Wind', level[0] if level is not None else None)

        self.msg.done()
        return q

    def drawWindBarb(self, level=None, lowRes=16, length=5, zorder=3, color='k', addTitle=True,  **kwargs):
        """
        绘制风矢量图。风羽一半长度表示2m/s，满长表示4m/s，三角表示20m/s。
        因为风矢量图标较大，所以大范围绘图时，lowRes需调大，不然太密集。
        @param level:int 气压层，若为ERA5资料为单层，则此参数无效。
        @param lowRes:int 数据采样间隔数。
        @param length:int 风矢量整体大小，默认为6。
        @param zorder:int 图层高度，默认为55。
        @param color:str 风矢量颜色，默认为黑色。
        @param **kwargs: 绘制矢量 matplotlib.pyplot.barb() 的其他参数。
        @return: barb:matplotlib.pyplot.barb() 的返回值。
        """

        self.msg.log("正在绘制风矢量")
        level = self.setPreslevel(level)

        # 获取数据
        u = self.reader.getSubset('u', self.timeIndex, self.timeIndex+1,
                                  self.dataRange[0], self.dataRange[1], self.dataRange[2], self.dataRange[3], level, lowRes=lowRes)
        v = self.reader.getSubset('v', self.timeIndex, self.timeIndex+1,
                                  self.dataRange[0], self.dataRange[1], self.dataRange[2], self.dataRange[3], level, lowRes=lowRes)
        lon, lat = self.reader.getSubsetLonLat(
            self.dataRange[0], self.dataRange[1], self.dataRange[2], self.dataRange[3], lowRes=lowRes)

        # 画图
        q = plt.barbs(lon, lat, u, v, color=color, zorder=zorder, transform=ccrs.PlateCarree(), length=length,
                      barb_increments={"half": 2, "full": 4, "flag": 20}, sizes={'spacing': 0.2}, **kwargs)
        # set title
        if addTitle:
            self.setTitle('Wind', level[0] if level is not None else None)

        self.msg.done()
        return q

    def drawGradient(self, name, level=None, value=None, lowRes=1, cmap="RdPu", km=True, addTitle=True,  **kwargs):
        """
        绘制某要素的水梯度平分布图。
        @param name:str 要素名称，例如位势'z'，温度't'。
        @param level:int 气压层，若为ERA5资料为单层，则此参数无效。
        @param lowRes:int 数据采样间隔数。
        @param zorder:int 图层高度，默认为19。
        @param cmap:str 图颜色。
        @param colorbar:bool 是否绘制颜色标尺，默认为True。
        @param colorbarLabel:str 颜色标尺标签。
        @param ks:bool 是否以 km 为单位。
        @param **kwargs: matplotlib.pyplot.contourf() 的其他参数。
        @return ERA5Plotter.drawContour()的返回值
        """
        self.msg.log("正在绘制 %s 水梯度平分布图" % name)

        # adject land variable
        if name in self.reader.meta.keys():
            land = False
        elif name in self.reader.landMeta.keys():
            land = True
        else:
            raise Exception("数据中无此变量: " + name)

        # set level
        if level is not None:
            level = self.setPreslevel(level)

        # set label
        e = self.reader.data.variables[name]
        colorbarLabel = self.setLabel(
            name, level, land, e.units+" km**-1" if km else e.units + " m**-1", gradient=True)

        # get data
        d = self.reader.getSubset(name, self.timeIndex, self.timeIndex+1,
                                  self.dataRange[0], self.dataRange[1], self.dataRange[2], self.dataRange[3], level, lowRes=lowRes)
        lon, lat = self.reader.getSubsetLonLat(
            self.dataRange[0], self.dataRange[1], self.dataRange[2], self.dataRange[3], lowRes=lowRes)

        d = self.calcGradient(d, lon, lat, lowRes)

        # km
        if km:
            d *= 1000

        if value is None:
            value = self.setValue(value, d)

        # draw
        c = self.drawContour(lon=lon, lat=lat, data=d, cmap=cmap, value=value,
                             colorbarLabel=colorbarLabel, addTitle=False, **kwargs)

        # set title
        if addTitle:
            self.setTitle(
                name, level[0] if level is not None else None, suffix="Gradient")

        self.msg.done()
        return c

    def createSection(self, subplot=(1, 1, 1), loc=0, dir='x', lim=None, geoHeight=False, level=None, geoDagpm=True, geoPres=True, geoPresColor='k', geoPresStyle='--', geoPresWidth=0.8, geoPresZero=True, fontsize=10, maxMin=True):
        """
        创建一个剖面子图，是绘制剖面图的前提。
        @param subplot:`list, tuple` 子图位置，需传入三元数组，与 plt.add_subplot() 相同。
        @param loc:float 剖面的位置，若纬向剖面，则为剖面的纬度，经向剖面则为经度。
        @param dir:str 剖面方向，纬向为'x'，经向为'y'，默认为纬向。
        @param lim:list, tuple ，剖面范围，两个元素的数组，分别是[起始经纬度，结束经纬度]，若为纬向剖面，则为剖面的经度范围，经向剖面则是纬度范围。默认使用dataSetting()范围。
        @param geoHeight:bool 是否使用位势高度坐标，不使用则为气压坐标。
        @param geoDagpm:bool 位势高度坐标使用什位势米(dagpm)为单位。
        @param geoPres:bool 在位势高度坐标上绘制等压面。
        @param geoPresColor:str 等压面等值线颜色。
        @param geoPresStyle:str 等压面等值线样式，默认虚线。
        @param geoPresWidth:float 等压面等值线线宽，默认0.8。
        @param geoPresZero:bool 是否绘制0位势高度横线。
        @param fontsize:float 等压面等值线标签字体大小
        @param maxMin:bool 使用位势高度坐标时，是否使气压层做大最小值顶格到画框边缘（充满画布）
        @return ERA5Plotter.ax 当前子图
        """
        self.msg.log("正在绘制剖面图底图")

        if lim is None:
            lim = (self.dataRange[0], self.dataRange[1]) if dir == 'x' else (self.dataRange[2], self.dataRange[3])
        
        # 添加子图
        self.ax = self.figure.add_subplot(subplot[0], subplot[1], subplot[2])
        self.geoHeight = geoHeight
        self.sectionLevel = level
        self.sectionLoc = loc
        self.sectionDir = dir
        self.sectionLim = lim

        x, y, l, z = self.reader.getSection(
            'z', self.timeIndex, loc, dir, lim, level, lowRes=1)
        if geoHeight:

            # calculate coordinate
            y = z / (98 if geoDagpm else 9.8)
            x = np.array([x for i in range(len(y))])
            self.geoHeightX = x
            self.geoHeightY = y

            # set Figure
            if maxMin:
                plt.ylim(np.min(y), np.max(y))
            if geoPresZero:
                if lim is None:
                    hlineX = (self.reader.lon[0], self.reader.lon[-1]) if dir == 'x' else (
                        self.reader.lat[-1], self.reader.lat[0])
                else:
                    hlineX = lim
                plt.hlines(
                    0, xmin=hlineX[0], xmax=hlineX[1], linewidths=1, colors='k')

            # get element data
            if geoPres:
                pres = np.array([np.ones(z.shape[1])*l[i]
                                for i in range(len(l))])

        if geoHeight:
            pass
        else:
            plt.yticks(y, l)

        # draw Pres
        if geoHeight and geoPres:
            presc = plt.contour(x, y, pres, l,  colors=geoPresColor,
                                linestyles=geoPresStyle, linewidths=geoPresWidth)
            plt.clabel(presc, fmt='%.0f', fontsize=fontsize)

        # set label
        plt.ylabel(("Geopotentia Height "+("(dagpm)" if geoDagpm else "(gpm)"))
                   if geoHeight else "Pressure (hPa)")
        plt.xlabel(("Longitude (DEG)" if dir == 'x' else "Latitude (DEG)") + " at " + str(abs(loc)) +
                   (("°E" if dir == 'y' else "°N") if loc >= 0 else ("°W" if dir == 'y' else "°S")))

        self.msg.done()
        return self.ax

    def drawSection(self, name, value=None, lowRes=1, cmap=None,
                    contourf=False, color='k', linewidth=1, label=True, colorbar=True, fontsize=10, fmt="%.1f",
                    CTemp=True, perks=True, dagpm=True, extend=None, zorder=None, colorbarLabel=None, units=None, addTitle=True, gpkg=True, **kwargs):
        """
        绘制某元素的经向/纬向垂直分布等值图。
        大部分参数与 drawContour() 相同。数据范围使用 createSection() 设置。
        @param name:str **必填** 变量名，暂不支持输入自定数据。
        @return plt.contour 或 plt.contourf 的返回值。
        """

        self.msg.log("正在绘制剖面图")
        self.titleText = []
        # set name
        if name in self.paramName.keys():
            name = self.paramName[name]
        if name not in self.reader.keys:
            raise Exception("数据中没有此变量："+name)

        # get data
        loc = self.sectionLoc
        dir = self.sectionDir
        lim = self.sectionLim
        level = self.sectionLevel
        if self.geoHeight:
            xx, yy, ll, d = self.reader.getSection(
                name, self.timeIndex, loc, dir, lim, level, lowRes)
            x = self.geoHeightX[:, ::lowRes]
            y = self.geoHeightY[:, ::lowRes]
        else:
            x, y, l, d = self.reader.getSection(
                name, self.timeIndex, loc, dir, lim, level, lowRes)

        # set value
        if value is None:
            value = 20

        # set cmap
        if cmap is None:
            if name in self.cmap.keys():
                cmap = self.cmap[name]
            else:
                cmap = 'viridis'

        # set contourf extend mode
        if extend is None:
            extend = 'both'

        # set zorder
        if zorder is None:
            zorder = 1 if contourf else 4

        # use ℃ in temperature
        if name == 't' or name == 't2m' and CTemp:
            d -= 273.15
            units = "℃"

        # use 1/1000s in vorticity and divergence
        if name == 'vo' or name == 'd' and perks:
            d *= 1000
            units = "10**-3 s**-1"

        # set fmt
        if fmt is None:
            if name in self.labelFmt.keys():
                fmt = self.labelFmt[name]
            else:
                fmt = "%.1f"

        # set height
        if name == 'z' and self.geoHeight == False:
            if dagpm:
                d /= 98
                value = np.arange(0, 1000, 4)
            else:
                d /= 9.8
                value = np.arange(0, 10000, 40)

        # set q
        if name == 'q' and gpkg:
            d *= 1000

        # draw data
        if contourf:
            if colorbarLabel is None:
                colorbarLabel = self.setLabel(name, level, unit=units)

            c = plt.contourf(x, y, d, value, cmap=cmap,
                             extend=extend, **kwargs)
            if colorbar:
                plt.colorbar(c, orientation='vertical', pad=self.colorbarConfig['pad'],
                             fraction=self.colorbarConfig['fraction'], shrink=self.colorbarConfig['shrink'], label=colorbarLabel)
        else:
            c = plt.contour(x, y, d, value, colors=color,
                            linewidths=linewidth, **kwargs)
            if label:
                plt.clabel(c, fontsize=fontsize, fmt=fmt)

        # set title
        if addTitle:
            self.setTitle(name)

        self.msg.done()
        return c

    def drawSectionWindArrow(self,  amp=100, lowRes=8, zorder=3, color='k', width=0.002, keyX=0.9, keyY=0.9, keyValue=10, addTitle=True, **kwargs):
        """
        绘制风箭头图。若绘制多张风箭头图，请使用**kwargs传入参数使风箭头缩放倍数固定。
        大部分参数与 drawWindArrow() 相同。
        ------  注意  ------
        ※※垂直速度单位为 Pa/s，此处绘图并未转换为 m/s!
        ※※因此绘图结果并不能完全表示真实情况!
        --------------------
        @param amp:`float` 垂直速度放大倍数。
        @param **kwargs: 绘制箭头 matplotlib.pyplot.quiver() 的其他参数。
        @return: quiver:matplotlib.pyplot.quiver() 的返回值。
        """

        self.msg.log("正在绘制剖面风矢量")

        # level = self.setPreslevel(level)
        loc = self.sectionLoc
        lim = self.sectionLim
        level = self.sectionLevel
        dir = self.sectionDir

        # get data
        if self.geoHeight:
            xx, yy, ll, u = self.reader.getSection(
                'u' if dir == 'x' else 'v', self.timeIndex, loc, dir, lim, level, lowRes)
            xx, yy, ll, w = self.reader.getSection(
                'w', self.timeIndex, loc, dir, lim, level, lowRes)
            x = self.geoHeightX[:, ::lowRes]
            y = self.geoHeightY[:, ::lowRes]
        else:
            x, y, l, u = self.reader.getSection(
                'u' if dir == 'x' else 'v', self.timeIndex, loc, dir, lim, level, lowRes)
            x, y, l, w = self.reader.getSection(
                'w', self.timeIndex, loc, dir, lim, level, lowRes)

        # draw Wind
        q = plt.quiver(x, y, u, -w*amp, color=color, width=width,
                       zorder=zorder,  **kwargs)
        plt.quiverkey(q, keyX, keyY, keyValue, str(keyValue) +
                      ' m/s', labelpos='E', coordinates='figure')
        # set title
        if addTitle:
            self.setTitle('Wind', level[0] if level is not None else None)

        self.msg.done()
        return q

    def drawSectionWindBarb(self, lowRes=16, length=5, zorder=3, color='k', addTitle=True,  **kwargs):
        """
        绘制垂直剖面上的**水平**风矢量图。风羽一半长度表示2m/s，满长表示4m/s，三角表示20m/s。
        因为风矢量图标较大，所以大范围绘图时，lowRes需调大，不然太密集。
        ------  注意  ------
        ※※垂直速度单位为 Pa/s，此处绘图并未转换为 m/s!
        ※※因此绘图结果并不能完全表示真实情况!
        --------------------
        大部分参数与 drawWindBard() 相同。
        @param **kwargs: 绘制箭头 matplotlib.pyplot.quiver() 的其他参数。
        @return: barb:matplotlib.pyplot.barb() 的返回值。
        """

        self.msg.log("正在绘制剖面风矢量")
        loc = self.sectionLoc
        lim = self.sectionLim
        level = self.sectionLevel
        dir = self.sectionDir

        # get data
        if self.geoHeight:
            xx, yy, ll, u = self.reader.getSection(
                'u', self.timeIndex, loc, dir, lim, level, lowRes)
            xx, yy, ll, v = self.reader.getSection(
                'v', self.timeIndex, loc, dir, lim, level, lowRes)
            x = self.geoHeightX[:, ::lowRes]
            y = self.geoHeightY[:, ::lowRes]
        else:
            x, y, l, u = self.reader.getSection(
                'u', self.timeIndex, loc, dir, lim, level, lowRes)
            x, y, l, v = self.reader.getSection(
                'v', self.timeIndex, loc, dir, lim, level, lowRes)

        # 画图
        q = plt.barbs(x, y, u, v, color=color, zorder=zorder,  length=length,
                      barb_increments={"half": 2, "full": 4, "flag": 20}, sizes={'spacing': 0.2}, **kwargs)
        # set title
        if addTitle:
            self.setTitle('Wind', level[0] if level is not None else None)

        self.msg.done()
        return q

    def getData(self, name, level=None, timeIndex=None, lowRes=1):
        """
        获取数据。数据时间空间范围通过dataSetting()设置
        @param name:str 要素名称，例如位势'z'，温度't'。
        @return:np.ndarray 2d数据。
        """
        level = self.setPreslevel(level)
        self.lowRes = lowRes

        if timeIndex is not None:
            t0 = timeIndex[0]
            t1 = timeIndex[1]
        else:
            t0 = self.timeIndex
            t1 = self.timeIndex+1
        if name in self.paramName.keys():
            name = self.paramName[name]

        return self.reader.getSubset(name, t0, t1, self.dataRange[0], self.dataRange[1], self.dataRange[2], self.dataRange[3], level, lowRes=lowRes)

    def title(self, text=None, time=True, fontsize=10, timeZone=8):
        """
        设置子图标题。
        @param text:str 标题文本。
        @param time:bool 是否显示时间。
        """
        if text is None:
            text = " & ".join(self.titleText)
        plt.title(text, fontsize=fontsize, loc='left')

        if time:
            plt.title(self.reader.convert_time(
                self.reader.time[self.timeIndex], fmt="%Y-%m-%d %H:%M", timeZone=timeZone)+(" CST" if timeZone == 8 else " UTC%d" % timeZone), fontsize=fontsize, loc='right')

        return None

    def suptitle(self, text, fontsize=14, y=0.95):
        """
        设置标题。
        @param text:str 标题文本。
        @param fontsize:int 标题字体大小。
        @param y:float 标题位置。
        """
        self.figure.suptitle(text, fontsize=fontsize, y=y)
        return None

    def timeSeries(self, startFmtTime, EndFmtTime, interval=6, inputFmt="%Y%m%d%H", outputFmt="%Y%m%d%H"):
        st = datetime.strptime(startFmtTime, inputFmt)
        et = datetime.strptime(EndFmtTime, inputFmt)
        time_series = []
        t = st
        while t <= et:
            time_series.append(t.strftime(outputFmt))
            t += timedelta(hours=interval)
        return time_series

    def save(self, path, **kwargs):
        self.msg.log("正在保存图像")
        plt.savefig(path, **kwargs)
        self.msg.done()

    def presetFigure(self, mapRange, time, timeFmt="%Y%m%d%H",show=True, savePath=None):
        self.createFigure(figsize=(22,10))
        plt.subplots_adjust(left=0.025, right=0.97, top=0.98, bottom=0.05, wspace=0.18, hspace=0.1)
        self.dataSetting(time, timeFmt=timeFmt, dataRange=mapRange)


        self.drawMap((2,3,1), coastalLineColor='#444444', mapRange=mapRange)
        self.drawContour('z', level=500, color='r')
        self.drawContour('z', level=850, color='b')
        self.drawWindArrow(850, color='g')
        self.title(time=False)

        self.drawMap((2,3,2), coastalLineColor='#444444', mapRange=mapRange)
        self.drawContour('z', 500)
        self.drawContour('t', 850, contourf=True, value=np.linspace(-25, 15, 21))
        self.title(time=False)
        
        self.drawMap((2,3,3), coastalLineColor='#444444', mapRange=mapRange)
        self.drawContour('z', 500)
        self.drawWindArrow(850, color='g')
        self.drawContour('w', 850, contourf=True)
        self.title(time=False)

        self.drawMap((2,3,4), coastalLineColor='#444444', mapRange=mapRange)
        # self.drawWindArrow(850, color='g')
        self.drawContour('prec', contourf=True)
        # self.drawContour('sp',)
        self.title(time=False)

        self.createSection((2,3,5), 115, dir='y', geoHeight=True)
        self.drawSection('q', linewidth=0.8, fmt='%.0f', value=np.linspace(0, 20, 21))
        self.drawSection('t',contourf=True)
        self.drawSectionWindArrow(color='g', lowRes=4)
        self.title(time=False)

        self.createSection((2,3,6), 30, dir='x', geoHeight=True)
        # self.drawSection('t')
        self.drawSection('t',contourf=True)
        self.drawSectionWindArrow(color='g', lowRes=4)
        self.title(time=False)

        self.suptitle(self.strTime + "(CST)  ERA5", y=1)
        if show:
            self.show()
        
        if savePath is not None:
            self.save(savePath)
        plt.close()